--[[
    Soundtrack addon for World of Warcraft

    Soundtrack events functions.
    Functions that handle mapping named events with tracks in the library.
]]

Soundtrack.MaxStackLevel = 10

local function verifyStackLevel(stackLevel)
    if not stackLevel or (not (stackLevel >= 1 and stackLevel <= Soundtrack.MaxStackLevel)) then
        Soundtrack.Error("BAD STACK LEVEL "..stackLevel)
    end
end

local nextTrack -- Hack because of fading issue


-- Attempts to play a random track on a specific event table. Returns true if it found a track to play.
function PlayRandomTrackByTable(tableName, eventName, offset)
    
    Soundtrack.TraceEvents("PlayRandomTrackByTable (" .. tableName .. ", " .. eventName .. ")")
    local eventTable = Soundtrack.Events.GetTable(tableName)
    
    if eventTable[eventName] then
        local trackList = eventTable[eventName].tracks
        
        if trackList then
            local numTracks = table.getn(trackList)
            if numTracks >= 1 then
                local index
                
                if eventTable[eventName].random and tableName ~= "Playlists" then
                    --Soundtrack.TraceEvents("Random")
                    index = random(1, numTracks)
                    --Soundtrack.TraceEvents("Random index: "..index)
                
                    -- Avoid playing same track twice
                    if index == eventTable[eventName].lastTrackIndex then
                        index = index + 1
                        if index > numTracks then
                            index = 1
                        end
                    end
                    --Soundtrack.TraceEvents("Adjusted Random index: "..index)
                else
                    --Soundtrack.TraceEvents("Non random")
                    -- Non random playback
                    if not eventTable[eventName].lastTrackIndex then
                        index = 1
                    else
                        index = eventTable[eventName].lastTrackIndex + offset
                        if index > numTracks then
                            index = 1
                        elseif index == 0 then
                            index = numTracks
                        end
                    end
                end
                
                local trackName = trackList[index]
                eventTable[eventName].lastTrackIndex = index
                if not Soundtrack.Events.Paused then
                    Soundtrack.Library.PlayTrack(trackName, eventTable[eventName].soundEffect)
		    nextTrack = trackName
                else
                    Soundtrack.TraceEvents("Paused")
                end
                return true
            end
        end
    end
    return false
end

StaticPopupDialogs["SOUNDTRACK_REMOVEEVENT"] = {
    text = [[Do you want to remove this event?]],
    button1 = "OK",
    button2 = "Cancel",
    OnAccept = function() 
        Soundtrack.Events.DeleteEvent("Zone", SoundtrackFrame_SelectedEvent) 
    end,
    timeout = 0,
    whileDead = 1,
    hideOnEscape = 1
}

-- Timer callback function
local cooldown = 0
local fadeOutTime = 2 

local function trackFinished()
    Soundtrack.Events.RestartLastEvent()
end

local function startEmptyTrack()
    Soundtrack.Library.PauseMusic()
end

local function playOnceTrackFinished()
    Soundtrack.StopEventAtLevel(Soundtrack.Events.GetCurrentStackLevel()) -- TODO Anthony : by name?
end

local function getTrackCount(event)
    if not event then
        return 0
    end
    
    local trackList = event.tracks
    if trackList then
        return table.getn(trackList)
    else
        return 0
    end
end

-- Returns the current stack level on which a valid event is found.
local function getValidStackLevel()
    local validStackLevel = 0
    
    for i = table.getn(Soundtrack.Events.Stack), 1, -1 do
        local event = Soundtrack.GetEvent(Soundtrack.Events.Stack[i].tableName, Soundtrack.Events.Stack[i].eventName)
        if event then
            local trackCount = getTrackCount(event)
            if validStackLevel == 0 and trackCount > 0 then
                validStackLevel = i
            elseif not event.continuous then
                Soundtrack.TraceEvents("Removing obsolete event: "..Soundtrack.Events.Stack[i].eventName)
                Soundtrack.Events.Stack[i].eventName = nil
                Soundtrack.Events.Stack[i].tableName = nil
                Soundtrack.Events.Stack[i].offset = 0
            end
        else
            Soundtrack.Events.Stack[i].eventName = nil
            Soundtrack.Events.Stack[i].tableName = nil
            Soundtrack.Events.Stack[i].offset = 0
        end
    end
    
    return validStackLevel
end




local currentTableName = nil
local currentEventName = nil



function Soundtrack.UnRegisterEvent(eventName)
end

Soundtrack.Events.Stack = 
{ 
    { eventName = nil, tableName = nil }, -- Level 1: Continent
    { eventName = nil, tableName = nil }, -- Level 2: Region
    { eventName = nil, tableName = nil }, -- Level 3: Zones
    { eventName = nil, tableName = nil }, -- Level 4: Interiors
    { eventName = nil, tableName = nil }, -- Level 5: Misc. Stealth, Mount, Flight, Dance, Victory
    { eventName = nil, tableName = nil }, -- Level 6: Battle
    { eventName = nil, tableName = nil }, -- Level 7: Boss 
    { eventName = nil, tableName = nil }, -- Level 8: Level up, Death, Ghost
    { eventName = nil, tableName = nil }, -- Level 9: Playlists
    { eventName = nil, tableName = nil }, -- Level 10: Preview

}

Soundtrack.Events.Paused = false

-- Table of custom events defined by the user
Soundtrack.Events.CustomEvents = { }

-- Called anytime the stack has changed. Makes sure the top most event gets played,
-- and avoids playing unnecessary events.
function Soundtrack.Events.OnStackChanged(forceRestart)
    -- Remove any playOnce events that do not have any valid tracks or they will never be removed
    SoundtrackFrame_TouchEvents()
    
    local validStackLevel = getValidStackLevel()
    if validStackLevel == 0 then
        -- Nothing to play.
        -- Stop events if something was already active
        if currentTableName then
            Soundtrack.Timers.Remove("FadeOut")
            Soundtrack.Timers.Remove("TrackFinished")
            Soundtrack.Library.StopTrack()
        end
        currentTableName = nil
        currentEventName = nil
    
    else
        -- There is something valid on the stack.
        local stackItem = Soundtrack.Events.Stack[validStackLevel]
        if not stackItem then
            error("BAD DATA"..validStackLevel)
        end
        local tableName = Soundtrack.Events.Stack[validStackLevel].tableName
        local eventName = Soundtrack.Events.Stack[validStackLevel].eventName
        local event = Soundtrack.GetEvent(tableName, eventName)
        local offset = Soundtrack.Events.Stack[validStackLevel].offset
    
        -- Avoid restarting already playing event
        if forceRestart or 
            currentTableName ~= tableName or 
            currentEventName ~= eventName then

            -- We are starting a new track! 
            -- Remove the playback continuity timers
            Soundtrack.Timers.Remove("FadeOut")
            Soundtrack.Timers.Remove("TrackFinished")
	    nextTrack = null
            local res = PlayRandomTrackByTable(tableName, eventName, offset)
            if not res then
                Soundtrack.TraceEvents("Not supposed to play invalid events.")
            end    
            
            currentTableName = tableName
            currentEventName = eventName
            
            -- A track is now playing, register continuity timers
            if nextTrack then
                local track = Soundtrack_Tracks[nextTrack]
                if track then
                    local length = track.length
                    if length then 
                        if not event.continuous then
                            Soundtrack.Timers.AddTimer("FadeOut", length - fadeOutTime, playOnceTrackFinished)
                        else
                            local randomSilence = 0
                            if Soundtrack.Settings.Silence > 0 then
                                randomSilence = random(5, Soundtrack.Settings.Silence)
                            end
                            Soundtrack.Timers.AddTimer("TrackFinished", length + randomSilence, trackFinished)
                            Soundtrack.Timers.AddTimer("FadeOut", length - fadeOutTime, startEmptyTrack)
                        end
                    end        
                end
            end
        end
    end

end

-- Returns the event table for a tab name.
function Soundtrack.Events.GetTable(eventTableName)

    if not eventTableName then
        return
    end
    
    local eventTable = Soundtrack_Events[eventTableName]
    if not eventTable then
        Soundtrack.Error("Attempt to access invalid event table ("..eventTableName..")")
        return nil
    end
    
    return eventTable
end

-- Adds an event to the events table. If no trackName is passed, the event is created empty.
-- If a trackName is passed it is added to that events track list.
function Soundtrack.Events.Add(eventTableName, eventName, trackName)
    if not eventName then
        return
    end

    local eventTable = Soundtrack.Events.GetTable(eventTableName)
    
    if not eventTable then
        Soundtrack.TraceEvents("Cannot find table : " .. eventTableName)
        return
    end
    
    if not eventTable[eventName] then 
        if not trackName then
            Soundtrack.TraceEvents("Add Event: " .. eventTableName .. ": " .. eventName)
        end
        
        eventTable[eventName] = { tracks = {}, lastTrackIndex = 0, random = true }  
        
        -- Because I cant figure out how to sort the hashtable...
        Soundtrack_SortEvents(eventTableName)
    end
    
    if trackName then
        table.insert(eventTable[eventName].tracks, trackName)
    end
end

-- Removes an event from the events table. 
function Soundtrack.Events.Remove(eventTableName, eventName, trackName)
    
    local eventTable = Soundtrack.Events.GetTable(eventTableName)
    
    local tracks = eventTable[eventName].tracks
    for i,tn in ipairs(tracks) do
        if tn == trackName then 
            table.remove(tracks, i)
            return
        end
    end
end

function Soundtrack.Events.RemoveEventWithConfirmation()
    -- Confirmation
    StaticPopup_Show("SOUNDTRACK_REMOVEEVENT")
end

function Soundtrack.Events.DeleteEvent(tableName, eventName)
    
    local table = Soundtrack_Events[tableName]
    if table then
        if table[eventName] then
            Soundtrack.TraceEvents("Removing event: "..eventName)
            table[eventName] = nil
        end
        Soundtrack_SortEvents(tableName)
    end
end

function Soundtrack.Events.RenameEvent(tableName, oldName, newName)
    if oldName == newName then
        return
    end

    local table = Soundtrack_Events[tableName]
    if table and table[oldName] then
        Soundtrack.TraceEvents("Renaming event: " .. oldName .. " => " .. newName)
        local event = table[oldName]
        table[newName] = event
        table[oldName] = nil
        Soundtrack_SortEvents(tableName)
    end     
    
    -- Also rename event in CustomEvents
    if Soundtrack_CustomEvents[oldName] then
        local event = Soundtrack_CustomEvents[oldName]
        Soundtrack_CustomEvents[newName] = event
        Soundtrack_CustomEvents[oldName] = nil
    end
    
    if Soundtrack_MiscEvents[oldName] then
        local event = Soundtrack_MiscEvents[oldName]
        Soundtrack_MiscEvents[newName] = event
        Soundtrack_MiscEvents[oldName] = nil
    end
end

function Soundtrack.Events.ClearEvent(eventTableName, eventName)
    Soundtrack.TraceEvents("ClearEvent("..eventTableName..", "..eventName)
    local eventTable = Soundtrack.Events.GetTable(eventTableName)
    
    eventTable[eventName].tracks = {}
end

-- Adds an event on the stack and refreshes active track
function Soundtrack.Events.PlayEvent(tableName, eventName, stackLevel, forceRestart, playOnce, offset)
    if eventName == nil then
        Soundtrack.TraceEvents("Attempting to PlayEvent with a nil name!")
        return
    end
    
    if not stackLevel then
        Soundtrack.TraceEvents("Cannot PlayEvent(" .. eventName .. "). It has no priority level.")
        return
    end
    
    verifyStackLevel(stackLevel)
    
    if not tableName then
        Soundtrack.TraceEvents("PlayEvent: Invalid table name")
        return
    end
    if not eventName then
        Soundtrack.TraceEvents("PlayEvent: Invalid event name")
        return
    end

    local event = Soundtrack.GetEvent(tableName, eventName)
    if not event then
        return
    end

    local playOnceText
    if event.continuous then
        playOnceText = "Loop"
    else 
        playOnceText = "Once"
    end
    Soundtrack.TraceEvents("PlayEvent("..tableName..", "..eventName..", "..stackLevel..") "..playOnceText)    

    if not offset then 
        offset = 1 
    end

    -- Add event on the stack    
    if event.soundEffect then
        -- Sound effects are never added to the stack
        PlayRandomTrackByTable(tableName, eventName, offset)
    else
        Soundtrack.Events.Stack[stackLevel].tableName = tableName
        Soundtrack.Events.Stack[stackLevel].eventName = eventName
        Soundtrack.Events.Stack[stackLevel].offset = offset
        Soundtrack.Events.OnStackChanged(forceRestart)
    end
end



function Soundtrack.Events.RestartLastEvent(offset)
    local stackLevel = getValidStackLevel()
    if stackLevel > 0 then
        Soundtrack.Events.PlayEvent(Soundtrack.Events.Stack[stackLevel].tableName, 
                                    Soundtrack.Events.Stack[stackLevel].eventName, 
                                    stackLevel, true, offset)
    end
end

function Soundtrack.Events.GetCurrentStackLevel()
    return getValidStackLevel()
end

function Soundtrack.Events.PlaybackNext()
    Soundtrack.Events.RestartLastEvent(1)
end

function Soundtrack.Events.PlaybackPrevious()
    Soundtrack.Events.RestartLastEvent(-1)
end

function Soundtrack.Events.PlaybackPlayStop()
    if Soundtrack.Events.Paused then
        Soundtrack.TraceEvents("All music unpaused")
        Soundtrack.Events.Paused = false
        Soundtrack.Events.OnStackChanged(true)
    else
        Soundtrack.TraceEvents("All music paused")
        Soundtrack.Events.Paused = true
        Soundtrack.Library.PauseMusic()
    end
    SoundtrackFrame_RefreshPlaybackControls()
end

function Soundtrack.Events.Pause(enable)
    Soundtrack.Events.Paused = enable
    SoundtrackFrame_RefreshPlaybackControls()
end


